package ru.pb.game.objects.storage;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import ru.pb.game.objects.model.GameObject;
import ru.pb.game.objects.model.clan.Clan;
import ru.pb.game.objects.model.items.Item;
import ru.pb.game.objects.model.players.Player;

/**
 * Общее "супер" хранилище для всех объектов GameObject,
 * объекты делятся на типы - максимум 32 (0 - 31), для каждого типа свое хранилище,
 * в каждом хранилище может хранится до 67 108 864 объектов (0 - 67108863),
 * каждому объекту назначается уникальный 64-битный индентификатор типа long
 * который бинарно складывается из objId + тип + индекс в хранилище
 * 
 * @author Drin & Diamond & Felixx
 */
public class ObjectsStorage
{
	private static final Logger _log = Logger.getLogger(ObjectsStorage.class);

	private final ObjectArray[] STORAGES = new ObjectArray[StorageId.ARRAY.length - 1];

	private static ObjectsStorage _instance;

	public static ObjectsStorage getInstance()
	{
		if(_instance == null)
			_instance = new ObjectsStorage();

		return _instance;
	}

	private ObjectsStorage()
	{
		for(StorageId t : StorageId.ARRAY)
		{
			if(t == StorageId.STORAGE_NONE)
				continue;

			STORAGES[t.ordinal()] = new ObjectArray(t);
		}
	}

	// ==================================================================================================
	// IStoredObjects Methods
	// ==================================================================================================

	public IStoreableObject get(long storedId)
	{
		StorageId STORAGE_ID;

		if(storedId == 0L || (STORAGE_ID = getStorageID(storedId)) == StorageId.STORAGE_NONE)
			return null;

		IStoreableObject result = get(STORAGE_ID).get(getStoredIndex(storedId));

		return (result != null && (result.getObjectId() == getStoredObjectId(storedId))) ? result : null;
	}

	public long put(IStoreableObject o)
	{
		StorageId STORAGE_ID = selectStorageID(o);

		return ((long) o.getObjectId() & 0xFFFFFFFFL) | (((long) STORAGE_ID.ordinal() << 32) & 0x1F00000000L) | (((long) get(STORAGE_ID).add(o) << 37) & 0xFFFFFFE000000000L);
		// return o.getObjectId() & 0xFFFFFFFFL | (STORAGE_ID & 0x1FL) << 32 | (STORAGES[STORAGE_ID].add(o) & 0xFFFFFFFFL) << 37;
	}

	public long putDummy(IStoreableObject o)
	{
		return objIdNoStore(o.getObjectId());
	}

	private StorageId selectStorageID(IStoreableObject o)
	{
		if(o instanceof Clan)
			return StorageId.STORAGE_CLANS;
		else if(o instanceof GameObject)
		{
			GameObject object = (GameObject) o;

			if(object instanceof Item)
				return StorageId.STORAGE_ITEMS;
			else if(object instanceof Player)
				return StorageId.STORAGE_PLAYERS;
		}

		return StorageId.STORAGE_OTHER;
	}

	public boolean isStored(long storedId)
	{
		StorageId STORAGE_ID;
		if(storedId == 0L || (STORAGE_ID = getStorageID(storedId)) == StorageId.STORAGE_NONE)
			return false;

		IStoreableObject o = get(STORAGE_ID).get(getStoredIndex(storedId));
		return o != null && o.getObjectId() == getStoredObjectId(storedId);
	}

	public long objIdNoStore(int objId)
	{
		return objId;
	}

	public long refreshId(IStoreableObject o)
	{
		return o.getObjectId() | o.getStoredId() >> 32 << 32;
	}

	public IStoreableObject remove(long storedId)
	{
		StorageId STORAGE_ID = getStorageID(storedId);
		return (STORAGE_ID == StorageId.STORAGE_NONE) ? null : get(STORAGE_ID).remove(getStoredIndex(storedId), getStoredObjectId(storedId));
	}

	public int getAllObjectsCount()
	{
		int result = 0;
		for(ObjectArray<?> storage : STORAGES)
		{
			if(storage != null)
				result += storage.getRealSize();
		}
		return result;
	}

	// ==================================================================================================
	// GameObjects Methods
	// ==================================================================================================

	public GameObject getAsGameObject(long storedId)
	{
		IStoreableObject object = get(storedId);
		if(object instanceof GameObject)
			return (GameObject) object;
		else
			return null;
	}

	public int getAllGameObjectsCount()
	{
		int result = 0;
		for(StorageId t : StorageId.ARRAY)
		{
			if( !t.isGameObject())
				continue;

			ObjectArray<?> storage = STORAGES[t.ordinal()];
			if(storage != null)
				result += storage.getRealSize();
		}
		return result;
	}

	public List<GameObject> getAllGameObjects()
	{
		List<GameObject> result = new ArrayList<GameObject>(getAllGameObjectsCount());

		for(StorageId t : StorageId.ARRAY)
		{
			if( !t.isGameObject())
				continue;

			ObjectArray<GameObject> storage = get(t);

			if(storage != null)
				storage.getAll(result);
		}
		return result;
	}

	public GameObject findGameObject(int objId)
	{
		GameObject result;
		for(StorageId t : StorageId.ARRAY)
		{
			if( !t.isGameObject())
				continue;

			ObjectArray<?> storage = STORAGES[t.ordinal()];
			if(storage != null && (result = (GameObject) storage.findByObjectId(objId)) != null)
				return result;
		}
		return null;
	}

	// ==================================================================================================
	// Players Methods
	// ==================================================================================================

	private ObjectArray<Player> getStoragePlayers()
	{
		return get(StorageId.STORAGE_PLAYERS);
	}

	public Player getAsPlayer(long storedId)
	{
		return (Player) get(storedId);
	}

	public Player getPlayer(String name)
	{
		return getStoragePlayers().findByName(name);
	}

	public Player getPlayer(int objId)
	{
		return getStoragePlayers().findByObjectId(objId);
	}

	public List<Player> getAllPlayers()
	{
		return getStoragePlayers().getAll();
	}

	public Iterable<Player> getAllPlayersForIterable()
	{
		return getStoragePlayers();
	}

	public int getAllPlayersCount()
	{
		return getStoragePlayers().getRealSize();
	}

	// ==================================================================================================
	// Items Methods
	// ==================================================================================================

	private ObjectArray<Item> getStorageItems()
	{
		return get(StorageId.STORAGE_ITEMS);
	}

	public Iterable<Item> getAllItemsForIterable()
	{
		return getStorageItems();
	}

	public Item getAsItem(long storedId)
	{
		IStoreableObject object = get(storedId);
		if(object instanceof Item)
			return (Item) object;
		else
			return null;
	}

	public Item getItem(int objId)
	{
		return getStorageItems().findByObjectId(objId);
	}

	// ==================================================================================================
	// Clans Methods
	// ==================================================================================================

	private ObjectArray<Clan> getStorageClans()
	{
		return get(StorageId.STORAGE_CLANS);
	}

	public Clan getAsClan(long storedId)
	{
		IStoreableObject object = get(storedId);
		if(object instanceof Clan)
			return (Clan) object;
		else
			return null;
	}

	public Clan getClan(int objId)
	{
		return getStorageClans().findByObjectId(objId);
	}

	public Clan getClanByPlayerObjectId(int obj_id)
	{
		if(obj_id <= 0)
			return null;

		// for(Clan clan : getAllClansForIterable())
		// {
		// if(clan != null && clan.isAnyMember(obj_id))
		// return clan;
		// }
		return null;
	}

	public Clan getClanByName(String clanName)
	{
		// if( !Utils.isMatchingRegexp(clanName, ServerProperties.CLAN_NAME_TEMPLATE))
		// return null;

		// for(Clan clan : getAllClansForIterable())
		// {
		// if(clan.getUnitName(SubUnitType.MAIN_CLAN).equalsIgnoreCase(clanName))
		// return clan;
		// }

		return null;
	}

	public Iterable<Clan> getAllClansForIterable()
	{
		return getStorageClans();
	}

	public List<Clan> getAllClans()
	{
		return getStorageClans().getAll();
	}

	public int getAllClansCount()
	{
		return getStorageClans().getRealSize();
	}

	// ==================================================================================================
	// Helpers Methods
	// ==================================================================================================
	@SuppressWarnings("unchecked")
	private <T extends IStoreableObject>ObjectArray<T> get(StorageId id)
	{
		return STORAGES[id.ordinal()];
	}

	private StorageId getStorageID(long storedId)
	{
		return StorageId.ARRAY[(int) (storedId >> 32) & 0x1F];
	}

	private int getStoredIndex(long storedId)
	{
		return (int) (storedId >> 37);
	}

	public int getStoredObjectId(long storedId)
	{
		return (int) storedId;
	}

	public void info()
	{
		synchronized(STORAGES)
		{
			for(ObjectArray<?> storage : STORAGES)
			{
				if(storage == null)
					continue;

				_log.info("Name: " + storage.toString() + "; Size/Real: " + storage.size() + "/" + storage.getRealSize() + "; Capacity/Init: " + storage.capacity() + "/" + storage.initialCapacity());
			}
		}
	}
}